/*
 * oled.c
 *
 *  Created on: May 30, 2021
 *      Author: kirk_
 */

#include "oled.h"
#include "kd_math.h"
#include "string.h"
#include "font.h"

extern I2C_HandleTypeDef OLED_I2C_Handle;

uint8_t OLED_Map[8][128];

void OLED_Delay(void)
{
	int i = 0xFFF;
	while(i--);
	return;
}

void OLED_Init(void)
{
	// TODO Initialize the OLED
	OLED_Write_Cmd(0xAE);   //display off
	OLED_Write_Cmd(0x20); //Set Memory Addressing Mode
	OLED_Write_Cmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
	OLED_Write_Cmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7
	OLED_Write_Cmd(0xc8); //Set COM Output Scan Direction
	OLED_Write_Cmd(0x00);//---set low column address
	OLED_Write_Cmd(0x10);//---set high column address
	OLED_Write_Cmd(0x40);//--set start line address
	OLED_Write_Cmd(0x81);//--set contrast control register
	OLED_Write_Cmd(0x7f);
	OLED_Write_Cmd(0xa1);//--set segment re-map 0 to 127
	OLED_Write_Cmd(0xa6);//--set normal display
	OLED_Write_Cmd(0xa8);//--set multiplex ratio(1 to 64)
	OLED_Write_Cmd(0x3F);//
	OLED_Write_Cmd(0xa4);//0xa4,Output follows RAM content;0xa5,Output ignores RAM content
	OLED_Write_Cmd(0xd3);//-set display offset
	OLED_Write_Cmd(0x00);//-not offset
	OLED_Write_Cmd(0xd5);//--set display clock divide ratio/oscillator frequency
	OLED_Write_Cmd(0xf0);//--set divide ratio
	OLED_Write_Cmd(0xd9);//--set pre-charge period
	OLED_Write_Cmd(0x22); //
	OLED_Write_Cmd(0xda);//--set com pins hardware configuration
	OLED_Write_Cmd(0x12);
	OLED_Write_Cmd(0xdb);//--set vcomh
	OLED_Write_Cmd(0x20);//0x20,0.77xVcc
	OLED_Write_Cmd(0x8d);//--set DC-DC enable
	OLED_Write_Cmd(0x14);//
	OLED_Write_Cmd(0xaf);//--turn on oled panel

	OLED_Clear(0);
	OLED_Refresh(0,128,0,8);
}

void OLED_Write_Len(uint8_t reg,uint8_t len,uint8_t *buf)
{
	// TODO Write several bytes to the memory

	HAL_I2C_Mem_Write(&OLED_I2C_Handle, OLED_WRITE, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 0xfff);
	//OLED_Delay();

}

void OLED_Read_Len(uint8_t reg,uint8_t len,uint8_t *buf)
{
	HAL_I2C_Mem_Read(&OLED_I2C_Handle, OLED_READ, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 0xfff);
	//OLED_Delay();

}

void OLED_Write_Byte(uint8_t reg, uint8_t data)
{
	// TODO Write one byte to the memory
	uint8_t W_Data=data;

	//while(__HAL_DMA_GET_COUNTER(OLED_I2C_Handle.hdmatx));
	//HAL_I2C_Mem_Write_DMA(&OLED_I2C_Handle, OLED_WRITE, reg, I2C_MEMADD_SIZE_8BIT, &W_Data, 1);

	HAL_I2C_Mem_Write(&OLED_I2C_Handle, OLED_WRITE, reg, I2C_MEMADD_SIZE_8BIT, &W_Data, 1, 0xfff);
	OLED_Delay();

}

void OLED_Write_Display(uint8_t data)
{
	// TODO
	uint8_t W_Data=data;
	HAL_I2C_Mem_Write(&OLED_I2C_Handle, OLED_WRITE, 0x40, I2C_MEMADD_SIZE_8BIT, &W_Data, 1, 0xfff);
}

uint8_t OLED_Read_Byte(uint8_t reg)
{
  unsigned char R_Data=0;

  HAL_I2C_Mem_Read(&OLED_I2C_Handle, OLED_READ, reg, I2C_MEMADD_SIZE_8BIT, &R_Data, 1, 0xfff);
  OLED_Delay();

  return R_Data;
}

void OLED_Write_Cmd(uint8_t cmd)
{
	// TODO Write a byte
	OLED_Write_Byte(0, cmd);
}

void OLED_Clear(uint8_t mode)
{
	// TODO
  uint8_t y,x;
  if(mode)
	  mode = 0xFF;
  for(y=0;y<8;y++)
  {
	for(x=0;x<128;x++)
	{
		OLED_Map[y][x] = mode;
	}
   }
}

void OLED_Set_Loc(uint8_t x, uint8_t y)
{
	// TODO
	OLED_Write_Cmd(0xb0+y);
	OLED_Write_Cmd(((x&0xf0)>>4)|0x10);
	OLED_Write_Cmd((x&0x0f));
}

void OLED_Refresh(int start_x, int end_x, int start_y, int end_y)
{
	// TODO
	uint8_t x, y;
	int tmp;

	if(start_x < 0 || start_x > OLED_WIDTH) return;
	if(end_x   < 0 || end_x   > OLED_WIDTH) return;
	if(start_y < 0 || start_y > OLED_HEIGHT) return;
	if(end_y   < 0 || end_y   > OLED_HEIGHT) return;

	if(start_x>end_x)
	{
		tmp = end_x;
		end_x = start_x;
		start_x = tmp;
	}
	if(start_y>end_y)
	{
		tmp = end_y;
		end_y = start_y;
		start_y = tmp;
	}
	for(y=start_y;y<end_y;y++)
	{
		OLED_Set_Loc(start_x, y);
		for(x=start_x;x<end_x;x++)
		{
			//while(__HAL_DMA_GET_COUNTER(OLED_I2C_Handle.hdmatx));
			//HAL_I2C_Mem_Write_DMA(&OLED_I2C_Handle, OLED_WRITE, 0x40, I2C_MEMADD_SIZE_8BIT, &OLED_Map[y][x], 1);
			HAL_I2C_Mem_Write(&OLED_I2C_Handle, OLED_WRITE, 0x40, I2C_MEMADD_SIZE_8BIT, &OLED_Map[y][x], 1, 0xfff);
			  OLED_Delay();
		}
	}
}

void OLED_SetPoint(int x, int y)
{
	// TODO
	uint8_t mask = 1 << (y&7);
	if(x>=OLED_WIDTH || y>=OLED_HEIGHT) return;
	if(x<0 || y<0) return;
	y >>= 3;
	OLED_Map[y][x] |= mask;
}

void OLED_ResetPoint(int x, int y)
{
	// TODO
	uint8_t mask = 1 << (y&7);
	if(x>=OLED_WIDTH || y>=OLED_HEIGHT) return;
	if(x<0 || y<0) return;
	y >>= 3;
	OLED_Map[y][x] &= ~mask;
}

void OLED_DrawPoint(int x, int y, uint8_t value)
{
	// TODO
	uint8_t mask = 1 << (y&7);
	if(x>=OLED_WIDTH || y>=OLED_HEIGHT) return;
	if(x<0 || y<0) return;
	y >>= 3;
	if(value)
		OLED_Map[y][x] |= mask;
	else
		OLED_Map[y][x] &= ~mask;
}

void OLED_SetPointRefresh(int x, int y)
{
	// TODO
	uint8_t mask = 1 << (y&7);
	if(x>=OLED_WIDTH || y>=OLED_HEIGHT) return;
	if(x<0 || y<0) return;
	y >>= 3;
	OLED_Map[y][x] |= mask;
	OLED_Refresh(x,x+1,y,y+1);
}

void OLED_ResetPointRefresh(int x, int y)
{
	// TODO
	uint8_t mask = 1 << (y&7);
	if(x>=OLED_WIDTH || y>=OLED_HEIGHT) return;
	if(x<0 || y<0) return;
	y >>= 3;
	OLED_Map[y][x] &= ~mask;
	OLED_Refresh(x,x+1,y,y+1);
}

void OLED_DrawPointRefresh(int x, int y, uint8_t value)
{
	// TODO
	uint8_t mask = 1 << (y&7);
	if(x>=OLED_WIDTH || y>=OLED_HEIGHT) return;
	if(x<0 || y<0) return;
	y >>= 3;
	if(value)
		OLED_Map[y][x] |= mask;
	else
		OLED_Map[y][x] &= ~mask;
	OLED_Refresh(x,x+1,y,y+1);
}

uint8_t OLED_GetCharWidth(uint8_t height)
{
	switch(height)
	{
	case 8:
	case 12:
		return 6;
	case 16:
		return 8;
	case 24:
		return 12;
	case 32:
		return 16;
	default:
		return 6;
	}
}

void OLED_ShowCharBase(int x, int y, char ch, uint8_t size, uint8_t mode, uint8_t backmode, uint8_t refresh)
{
	// TODO
	uint8_t num;
	int Horizontal = x;
	int Vertical = y;
	//int x = Horizontal, y = Vertical;
	uint8_t tmp;
	uint8_t i, j;
	uint8_t* pointer;

	uint8_t Total = (size/8+((size%8)?1:0))*(size/2);

	if(size == 8)
		Total = 6;
	mode = !mode;

	num = (uint8_t)(ch - ' ');

	switch(size)
	{
	case 8:
		pointer = (uint8_t*)asc2_0806[num];
		break;
	case 12:
		pointer = (uint8_t*)asc2_1206[num];
		break;
	case 16:
		pointer = (uint8_t*)asc2_1608[num];
		break;
	case 24:
		pointer = (uint8_t*)asc2_2412[num];
		break;
	case 32:
		pointer = (uint8_t*)asc2_3216[num];
		break;
	default:
		pointer = (uint8_t*)asc2_0806[num];
		size = 8;
	}

	for(i=0;i<Total;i++)
	{
		tmp = pointer[i];
		for(j=0;j<8;j++)
		{
			if(tmp & 0x80)
				OLED_DrawPoint(x,y,mode);
			else if(backmode)
				OLED_DrawPoint(x,y,!mode);
			tmp <<= 1;
			y++;
			if(y - Vertical == size)
			{
				y = Vertical;
				x++;
				break;
			}
		}
	}
	if(refresh)
	{
		int width;
		int start_y, start_x;
		int end_y, end_x;
		start_x = Horizontal;
		start_y = Vertical;

		if(size == 8)
			width = 6;
		else
			width = size>>1;

		end_x = start_x + width;
		end_y = start_y + size;

		start_y >>= 3;
		end_y = (end_y>>3) + ((end_y&7)!=0);
		OLED_Refresh(start_x, end_x, start_y, end_y);
	}
}

void OLED_ShowChar(int x, int y, char ch, uint8_t size)
{
	OLED_ShowCharBase(x,y,ch,size,0,1,0);
}

void OLED_ShowCharRefresh(int x, int y, char ch, uint8_t size)
{
	OLED_ShowCharBase(x,y,ch,size,0,1,1);
}


void OLED_ShowString(int Horizontal, int Vertical, char* String, uint8_t Size, uint8_t Mode)
{
	// TODO
	uint16_t Length = KD_Math_Min(22,strlen(String));
	uint8_t Width = OLED_GetCharWidth(Size);
	while(Length--)
	{
		OLED_ShowChar(Horizontal, Vertical, *String, Size);
		Horizontal += Width;
		String++;
	}
}

void OLED_ShowStringRefresh(int Horizontal, int Vertical, char* String, uint8_t Size, uint8_t Mode)
{
	// TODO
	uint16_t Length = KD_Math_Min(22,strlen(String));
	uint8_t Width = OLED_GetCharWidth(Size);
	while(Length--)
	{
		OLED_ShowCharRefresh(Horizontal, Vertical, *String, Size);
		Horizontal += Width;
		String++;
	}
}

void OLED_ShowNum(int Horizontal, int Vertical, uint32_t Number, uint8_t Length, uint8_t Size, uint8_t Mode)
{
	// TODO
	int x = Horizontal, y = Vertical;
	int Width = Length;
	uint8_t HalfSize = Size >> 1;
	Width *= HalfSize;
	x += Width;

	do
	{
		x -= HalfSize;
		OLED_ShowChar(x, y, (char)(Number % 10 + '0'), Size);
		Number /= 10;
	}while(x > Horizontal && Number);
}

void OLED_ShowNumRefresh(int Horizontal, int Vertical, uint32_t Number, uint8_t Length, uint8_t Size, uint8_t Mode)
{
	// TODO
	int x = Horizontal, y = Vertical;
	int Width = Length;
	uint8_t HalfSize = Size >> 1;
	Width *= HalfSize;
	x += Width;

	do
	{
		x -= HalfSize;
		OLED_ShowCharRefresh(x, y, (char)(Number % 10 + '0'), Size);
		Number /= 10;
	}while(x > Horizontal && Number);
}

void OLED_ShowDec(int Horizontal, int Vertical, uint32_t Dec, uint8_t Length, uint8_t Size, uint8_t Mode)
{
	// TODO

}

void OLED_ShowDecRefresh(int Horizontal, int Vertical, uint32_t Dec, uint8_t Length, uint8_t Size, uint8_t Mode)
{
	// TODO

}

void OLED_ShowSDec(int Horizontal, int Vertical, int32_t SDec, uint8_t Length, uint8_t Size, uint8_t Mode)
{
	// TODO

}

void OLED_ShowSDecRefresh(int Horizontal, int Vertical, int32_t SDec, uint8_t Length, uint8_t Size, uint8_t Mode)
{
	// TODO

}

void OLED_ShowHex(int Horizontal, int Vertical, uint32_t Hex, uint8_t Length, uint8_t Size, uint8_t Mode)
{
	// TODO

}

void OLED_ShowHexRefresh(int Horizontal, int Vertical, uint32_t Hex, uint8_t Length, uint8_t Size, uint8_t Mode)
{
	// TODO

}

void OLED_ShowFloat(int Horizontal, int Vertical, double Float, uint8_t Length, uint8_t DeciLength, uint8_t Size, uint8_t Mode)
{
	// TODO

}

void OLED_ShowFloatRefresh(int Horizontal, int Vertical, double Float, uint8_t Length, uint8_t DeciLength, uint8_t Size, uint8_t Mode)
{
	// TODO

}

void OLED_DrawLine(int HorizontalStart,int HorizonalEnd,int VerticalStart,int VerticalEnd,uint8_t Mode){
	// TODO Draw a line without refreshing the display
	double Horizontal,Vertical;
	double HorizontalDelta,VerticalDelta;
	double Length;

	int Step,Total;
	//Where to start
	Horizontal=HorizontalStart;
	Vertical=VerticalStart;
	//Horizontal and vertical length
	HorizontalDelta=HorizonalEnd-HorizontalStart;
	VerticalDelta=VerticalEnd-VerticalStart;
	//Total length
	Length=sqrt(pow(HorizontalDelta,2)+pow(VerticalDelta,2));
	//Total number of points
	//Draw a point every unit length
	//Can ensure no interrupted points
	Total=(int)(Length+1);
	//Step length
	HorizontalDelta/=Length;
	VerticalDelta/=Length;
	//Draw
	for(Step=0;Step<Total;Step++){
		OLED_DrawPoint((int)Horizontal,(int)Vertical,Mode);
		//Calculate the next point
		Horizontal+=HorizontalDelta;
		Vertical+=VerticalDelta;
	}
}

void OLED_DrawRectangular(int HorizontalStart,int HorizonalEnd,int VerticalStart,int VerticalEnd,uint8_t Mode)
{
	OLED_DrawLine(HorizontalStart,HorizontalStart,VerticalStart,VerticalEnd,Mode);
	OLED_DrawLine(HorizonalEnd,HorizonalEnd,VerticalStart,VerticalEnd,Mode);
	OLED_DrawLine(HorizontalStart,HorizonalEnd,VerticalStart,VerticalStart,Mode);
	OLED_DrawLine(HorizontalStart,HorizonalEnd,VerticalEnd,VerticalEnd,Mode);
}
